#include <mzfc_inc.h>
#include "CBCalcUiEdit.h"
#include "Pipe.h"
#include "Util.h"

#define READ_BUF_LEN     16

CBCalcUiEdit::CBCalcUiEdit()
{
    EnableDragModeH(true);
    EnableDragScroll(true);
    EnableDragScrollbarV(true);
    EnableScrollOptimize(true);

    EnableGridlines(false);
    EnableInsideScroll(true);
    EnableRichTextFormat(true);
    EnableUndo(true);
    EnableZoomIn(true);
    OpenFace(false);
    SetFontSize(MZFS_NORMAL);
    SetReadOnly(false);
    SetEditBgType(UI_EDIT_BGTYPE_FILL_WHITE);
    SetFontColor(RGB(0, 0, 0));

    int space = GetFontSize() / 10;
    if (space > 0)
        SetLineSpace(space);

    m_threadStdout = INVALID_HANDLE_VALUE;
    m_threadStderr = INVALID_HANDLE_VALUE;

    m_CalcPipe.open(TEXT("\\Program Files\\GNUbc\\GNUbc.exe"), NULL);
    if (! m_CalcPipe.isOpenned())
        m_CalcPipe.open(GetResourcePath(TEXT("GNUbc.exe")), NULL);

    DWORD bytesWritten;
    const char* initialSetting = "scale=4\n";
    m_CalcPipe.write(initialSetting, strlen(initialSetting), bytesWritten);
}

CBCalcUiEdit::~CBCalcUiEdit()
{
    const char* exp = "\nquit\n";
    DWORD bytesWritten;
    m_CalcPipe.write(exp, strlen(exp), bytesWritten);
    m_CalcPipe.close();

    if (WAIT_OBJECT_0 != WaitForSingleObject(m_threadStdout, 2000))
        TerminateThread(m_threadStdout, 0);
    if (WAIT_OBJECT_0 != WaitForSingleObject(m_threadStderr, 2000))
        TerminateThread(m_threadStderr, 0);
    CloseHandle(m_threadStdout);
    CloseHandle(m_threadStderr);
}

static DWORD WINAPI
stdout_threadproc(LPVOID arg)
{
    CBCalcUiEdit* ue = (CBCalcUiEdit*)arg;
    ue->postStdout();
    return 0;
}

static DWORD WINAPI
stderr_threadproc(LPVOID arg)
{
    CBCalcUiEdit* ue = (CBCalcUiEdit*)arg;
    ue->postStderr();

    return 0;
}

void
CBCalcUiEdit::postStdout()
{
    int* buf = new int[READ_BUF_LEN + 1];
    DWORD bytes;

    while (m_CalcPipe.read((char*)(buf + 1), READ_BUF_LEN * sizeof(int), bytes)) {
        if (0 == bytes)
            continue;

        buf[0] = (int)bytes;
        PostUiMessage(this, (LPARAM)buf);

        buf = new int[READ_BUF_LEN + 1];
    }

    delete[] buf;
}

void
CBCalcUiEdit::postStderr()
{
    int* buf = new int[READ_BUF_LEN + 1];
    DWORD bytes;

    while (m_CalcPipe.readError((char*)(buf + 1), READ_BUF_LEN * sizeof(int), bytes)) {
        if (0 == bytes)
            continue;

        buf[0] = 0 - (int)bytes;
        PostUiMessage(this, (LPARAM)buf);

        buf = new int[READ_BUF_LEN + 1];
    }

    delete[] buf;
}

void CBCalcUiEdit::UiWinProc(LPARAM lParam)
{
    int* buf = (int*)lParam;
    int len = buf[0];

    UiEdit::UiWinProc(lParam);

    if (len < 0) {  // stderr
        len = -len;
        SetFontColor(RGB(255, 0, 0));
    } else {        // stdout
        SetFontColor(RGB(0, 0, 255));
    }

    appendString((const char*)(buf + 1), len);

    SetFontColor(RGB(0, 0, 0));     // stdin

    delete[] buf;
}

void
CBCalcUiEdit::OnInit()
{
    ShowCaret();
    m_threadStdout = CreateThread(0, 0, stdout_threadproc, this, 0, 0);
    m_threadStderr = CreateThread(0, 0, stderr_threadproc, this, 0, 0);
}

int
CBCalcUiEdit::OnChar(TCHAR chCharCode, LPARAM lKeyData)
{
    int ret = UiEdit::OnChar(chCharCode, lKeyData);

    if (chCharCode == TEXT('\r')) {
        (void)sendExpression();
    }

    return ret;
}

void
CBCalcUiEdit::appendString(const char* buf, DWORD bytes)
{
    WCHAR tmp[36], *text;

    if (bytes > 32) {
        text = new WCHAR[bytes + 1];
        if (NULL == text)
            return;
    } else {
        text = tmp;
    }

    mbstowcs(text, buf, bytes);
    text[bytes] = 0;
    InsertString(text);

    if (tmp != text)
        delete[] text;
}

/**
 * @return:
 *      -1      error
 *      0       nothing send
 *      1       send something
 */
int
CBCalcUiEdit::sendExpression()
{
    CMzString text = GetText();
    size_t len = text.Length();
    int begin, end, ret;
    DWORD bytes;

#if 1
    size_t currentRow, totalRow, charCount, totalCount;
    DWORD showCount;
    int showHeight;

    GetCaretPos(&currentRow, NULL);
    totalRow = GetRowCount();
    totalCount = GetCharCount();
    charCount = GetCharCount(currentRow, 0, totalRow, 0);
    showCount = GetShowCount();
    showHeight = GetShowHeight();

    fprintf(stderr, "currentRow=%u, totalRow=%u, charCount=%u, totalCount=%u\n", currentRow, totalRow, charCount, totalCount);
    fprintf(stderr, "showCount=%u, showHeight=%u, text.length=%u\n", showCount, showHeight, text.Length());
#endif

    if (len == 0)
        return 0;

    if (text[len - 1] == TEXT('\n')) {
        end = len - 1;
        if (end == 0)
            return 0;
    } else {
        end = len;
    }

    begin = text.FindReverse(TEXT('\n'), len - end + 1);
    if (begin < 0)
        begin = 0;
    else
        ++begin;

    if (begin >= end)
        return 0;

    char tmp[68], *exp;
    CMzString t = text.SubStr(begin, end - begin);
    wchar_t* wexp = t.C_Str();
    size_t n = wcstombs(NULL, wexp, 0);

    if (n <= 0)
        return 0;

    if (n + 1 /* \n */ > 64)
        exp = new char[n + 4];
    else
        exp = tmp;

    n = wcstombs(exp, wexp, n);
    exp[n] = '\n';

    if (m_CalcPipe.write(exp, n + 1 /* \n */, bytes))
        ret = 1;
    else
        ret = 0;

    if (tmp != exp)
        delete[] exp;

    return ret;
}

